package com.bcx.wind.workflow.parser;

import com.bcx.wind.workflow.errorcontext.ErrorContext;
import com.bcx.wind.workflow.errorcontext.WindLocalError;
import com.bcx.wind.workflow.support.StreamHelper;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;

import static com.bcx.wind.workflow.message.ErrorCode.IO_ERROR;
import static com.bcx.wind.workflow.parser.ParserConstant.*;

/**
 * dtd helper
 *
 * @author zhanglei
 */
public final class XmlHelper {

    private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(XmlHelper.class);

    public static Document buildDoc(String context) {
        context = addDocumentDtdXml(context);
        try {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            factory.setValidating(true);

            factory.setNamespaceAware(false);
            factory.setIgnoringComments(true);
            factory.setIgnoringElementContentWhitespace(false);
            factory.setCoalescing(false);
            factory.setExpandEntityReferences(true);

            DocumentBuilder builder = factory.newDocumentBuilder();
            builder.setEntityResolver(new XmlResolver());
            builder.setErrorHandler(new ErrorHandler() {
                @Override
                public void error(SAXParseException exception) throws SAXException {
                    throw exception;
                }

                @Override
                public void fatalError(SAXParseException exception) throws SAXException {
                    throw exception;
                }

                @Override
                public void warning(SAXParseException exception) throws SAXException {
                }
            });
            InputSource source = new InputSource(new StringReader(context));
            return builder.parse(source);
        } catch (Exception e) {
            initErrorContent(e,  WindLocalError.get(), context);
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(e.getMessage(), e);
                LOGGER.debug("content: "+context);
            }
        }
        return null;
    }

    public static String  getStandardXml(String content){
        return addDocumentDtdXml(content);
    }

    private static InputStream addDocumentDtdXml(InputStream stream){
        String context = StreamHelper.getStr(stream);
        context = addDocumentDtdXml(context);
        return StreamHelper.strToStream(context);
    }

    /**
     * 添加dtd 约束xml
     */
    private static String  addDocumentDtdXml(String context){
        if(!context.contains(WORKFLOW_PROCESS_SYSTEM) || !context.contains(DOCTYPE)){
            StringBuilder newContext = new StringBuilder();
            StringReader reader = new StringReader(context);
            BufferedReader buf = new BufferedReader(reader);
            try {
                while (true) {
                    try {
                        String text = buf.readLine();
                        if (text == null) {
                            break;
                        }

                        if (text.contains(XML_DOC)) {
                            addNewContextDtd(newContext, text);
                        } else {
                            addNewContext(newContext, text, 0);
                        }

                    } catch (IOException e) {
                        e.printStackTrace();
                        WindLocalError.get()
                                .errorMsg("build process fail,because : "+e.getMessage())
                                .code(IO_ERROR);
                        break;
                    }
                }
            }finally {
                reader.close();
                try {
                    buf.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return newContext.toString();
        }
        return context;
    }

    private  static void  addNewContextDtd(StringBuilder newContext,String line){
        int index = line.indexOf(XML_DOC);
        newContext.append(line.substring(0,index+2))
                .append(System.lineSeparator())
                .append(DTD)
                .append(System.lineSeparator());

        line = line.substring(index+2,line.length()).trim();
        addNewContext(newContext,line,index);
    }


    private static  void addNewContext(StringBuilder newContext,String line,int index){
        while(true){
            if("".equals(line.trim())){
                break;
            }

            if((index = line.indexOf(XML_TAIL)) > -1){
                newContext.append(line.substring(0,index+1))
                        .append(System.lineSeparator());
                line = line.substring( index+1,line.length()).trim();

            }else if((index = line.indexOf(XML_TAIL_)) > -1){
                newContext.append(line.substring(0,index+2))
                        .append(System.lineSeparator());
                line = line.substring(index+2,line.length()).trim();
            }
        }
    }



    private static void initErrorContent(Exception e, ErrorContext errorContent, String context){
        if(e instanceof SAXParseException) {
            int errorLine = ((SAXParseException) e).getLineNumber();

            errorContent.errorMsg(e.getMessage())
                    .code(104);
            errorContent.processBuilderError()
                    .content(context)
                    .errorLine(errorLine);
        }
    }


    public static Document buildDoc(InputStream stream) {
        addDocumentDtdXml(stream);
        try{
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            factory.setValidating(true);

            factory.setNamespaceAware(false);
            factory.setIgnoringComments(true);
            factory.setIgnoringElementContentWhitespace(false);
            factory.setCoalescing(false);
            factory.setExpandEntityReferences(true);

            DocumentBuilder builder = factory.newDocumentBuilder();
            builder.setEntityResolver(new XmlResolver());
            builder.setErrorHandler(new ErrorHandler() {
                @Override
                public void error(SAXParseException exception) throws SAXException {
                    throw exception;
                }

                @Override
                public void fatalError(SAXParseException exception) throws SAXException {
                    throw exception;
                }

                @Override
                public void warning(SAXParseException exception) throws SAXException {
                }
            });
            InputSource source = new InputSource(stream);
            return builder.parse(source);
        } catch(Exception e){
            String context = StreamHelper.getStr(stream);
            initErrorContent(e, WindLocalError.get(), context);
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(e.getMessage(), e);
                LOGGER.debug("content: "+ context);
            }

        }
        return null;
    }
}
